package pl.smath.regex;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import pl.smath.expression.model.ExponentiationTerm;
import pl.smath.expression.model.FractionTerm;
import pl.smath.expression.model.NumberTerm;
import pl.smath.expression.model.ProductTerm;
import pl.smath.expression.model.RelationTerm;
import pl.smath.expression.model.SumTerm;
import pl.smath.expression.model.Term;
import pl.smath.expression.model.VariableTerm;
import pl.smath.regex.tree.vertexes.ConstVertex;
import pl.smath.regex.tree.vertexes.MinusVertex;
import pl.smath.regex.tree.vertexes.NumVertex;
import pl.smath.regex.tree.vertexes.RelationVertex;
import pl.smath.regex.tree.vertexes.SumIteratorVertex;
import pl.smath.regex.tree.vertexes.SumVertex;
import pl.smath.regex.tree.vertexes.VarVertex;
import pl.smath.regex.tree.vertexes.Vertex;
import pl.smath.regex.tree.vertexes.pair.AbsPairVertex;
import pl.smath.regex.tree.vertexes.pair.ExponentVertex;
import pl.smath.regex.tree.vertexes.pair.FractionVertex;
import pl.smath.regex.tree.vertexes.pair.PlusVertex;
import pl.smath.regex.tree.vertexes.pair.ProductVertex;

public class Matcher {

	private Expression goDeaper(Term term, Term l, Term r, AbsPairVertex vertex) {
		Expression left = match(l, vertex.getLeft());
		Expression right = match(r, vertex.getRight());
		
		for(int loop=0; loop<=1; loop++) {
			if (left != null && right != null) {
				Map<String, String> env = left.getEnv();
				
				for (Map.Entry<String, String> pair : right.getEnv().entrySet()) {
					String key = pair.getKey();
					if (env.containsKey(key)) {
						if (!env.get(key).equals(pair.getValue()))
							return null;
					} else env.put(key, pair.getValue());
				}

				return new Expression(term, vertex, env);
			}
			// we switch order and then try again
			right = match(r, vertex.getLeft());
			left = match(l, vertex.getRight());
		}
		
		return null;
	}
	
	private Expression match(NumberTerm term, NumVertex vertex) {	
		if (term.isNegative()) // matched negative value to positive!
			return null;
		
		HashMap<String, String> env = new HashMap<String, String>();
		env.put("num_" + vertex.getLabel(), term.getText());
		return new Expression(term, vertex, env);
	}
	
	private Expression match(NumberTerm term, ConstVertex vertex) {
		float l = Float.parseFloat(vertex.getConstant());
		float r = Float.parseFloat(term.getText());
		if (term.isNegative()) 
			r = -r;
		
		if (l == r)
			return new Expression(term, vertex);
		return null;
	}
	
	private Expression match(NumberTerm term, SumIteratorVertex vertex) {
		Expression exp = new Expression(term, vertex);
		exp.addEnv("SUM_ITER", term.getText());
		return exp;
	}
	
	private Expression match(NumberTerm term, ProductVertex vertex) {
		Vertex l = vertex.getLeft();
		Vertex r = vertex.getRight();
		
		for(int loop=0; loop<=1; loop++) {
			if (l instanceof NumVertex && r instanceof ExponentVertex && ((ExponentVertex)r).getRight() instanceof SumIteratorVertex) {
				Expression exp = match(term, l);
				exp.addEnv("SUM_ITER", "0");
				return exp;
			}
			// we switch order and then try again
			r = vertex.getLeft();
			l = vertex.getRight();
		}

		return null;
	}
	
	private Expression match(NumberTerm term, ExponentVertex vertex) {
		if (vertex.getLeft() instanceof VarVertex && vertex.getRight() instanceof SumIteratorVertex && term.toString().equals("1")) {
			Expression exp = new Expression(term, vertex.getLeft());
			exp.addEnv("SUM_ITER", "0");
			return exp;
		}
		return null;
	}
	
	private Expression match(VariableTerm term, VarVertex vertex) {
		HashMap<String, String> env = new HashMap<String, String>();
		env.put("var_" + vertex.getLabel(), term.getText());
		return new Expression(term, vertex, env);
	}
	
	
	private Expression match(VariableTerm term, ExponentVertex vertex) {
		if (vertex.getRight() instanceof SumIteratorVertex) {
			Expression exp = match(term, vertex.getLeft());
			exp.addEnv("SUM_ITER", "1");
			return exp;
		}
		return null;
	}
	
	private Expression match(SumTerm term, PlusVertex vertex) {
		List<Term> terms = term.getTerms();
		
		if (terms.size() != 2) {
			Term right = terms.get(terms.size()-1);
			SumTerm left = new SumTerm();
			left.addTerms(terms.subList(0, terms.size()-1));
			return goDeaper(term, left, right , vertex);
		}
		
		return goDeaper(term, terms.get(0), terms.get(1), vertex);
	}

	private Expression sumInside(Expression main, Term comp, SumVertex vertex) {
		// we want to match comp with inside expression in vertex
		// at the same time we are trying to find which sum index was used in this expression
		// after that we check the index with summing range
		
		Expression inside = match(comp, vertex.getInside());
		if (inside == null)
			return null;
		
		if (inside.getEnv().containsKey("SUM_ITER")) {
			int iter = Integer.parseInt(inside.getEnv().get("SUM_ITER"));
			
			if (!vertex.checkIndex(iter)) // index out of the range
				return null;
			
			Map<String, String> env = main.getEnv();
			for (Map.Entry<String, String> pair : inside.getEnv().entrySet()) {
				String key = pair.getKey();
				if (key.equals("SUM_ITER")) {
					key  = key + "_" + iter;
					if (env.containsKey(key)) // same index used twice
						return null;
				}
				
				if (key.endsWith("_" + vertex.getIter().toUpperCase())) {
					key  = key.substring(0, key.lastIndexOf('_')) + "_" + iter;
				}
				
				if (env.containsKey(key)) {
					if (!env.get(key).equals(pair.getValue())) // different value assigned to already defined label
						return null;
				} else env.put(key, pair.getValue());
			}
		} else {
			// we didn't find sum index
			return null;
		}
		return main;
	}
	
	private Expression match(SumTerm term, SumVertex vertex) {
		List<Term> terms = term.getTerms();
		Expression main = new Expression(term, vertex);
		
		for (Term comp : terms) {
			if (sumInside(main, comp, vertex) == null)
				return null;
		}
		
		return main;
	}
	
	private Expression match(Term term, SumVertex vertex) {
		Expression main = new Expression(term, vertex);
		if (sumInside(main, term, vertex) == null)
			return null;
		return main;
	}
	
	private Expression match(ProductTerm term, ProductVertex vertex) {
		List<Term> terms = term.getTerms();
		
		if (terms.size() != 2) {
			Term right = terms.get(terms.size()-1);
			ProductTerm left = new ProductTerm();
			left.addTerms(terms.subList(0, terms.size()-1));
			return goDeaper(term, left, right , vertex);
		}
		
		return goDeaper(term, terms.get(0), terms.get(1), vertex);
	}
	
	private Expression match(FractionTerm term, FractionVertex vertex) {
		return goDeaper(term, term.getNumerator(), term.getDenominator(), vertex);
	}
	
	private Expression match(ExponentiationTerm term, ExponentVertex vertex) {
		return goDeaper(term, term.getBase(), term.getExponent(), vertex);
	}
	
	private Expression match(ExponentiationTerm term, ProductVertex vertex) {
		Vertex l = vertex.getLeft();
		Vertex r = vertex.getRight();
		
		for(int loop=0; loop<=1; loop++) {
			if (l instanceof NumVertex && r instanceof ExponentVertex && ((ExponentVertex)r).getRight() instanceof SumIteratorVertex) {
				Expression exp = match(term, r);
				exp.addEnv("num_" + ((NumVertex)l).getLabel(), "1");
				return exp;
			}
			// we switch order and then try again
			r = vertex.getLeft();
			l = vertex.getRight();
		}
		
		return null;
	}
	
	private Expression match(RelationTerm term, RelationVertex vertex) {
		if (term.getText().equals(vertex.getRelation())) 
			return goDeaper(term, term.getLeftTerm(), term.getRightTerm(), vertex);
		return null;
	}
	
	/*
	 *  In case of not finding match we return null.
	 */
	public Expression match(Term term, Vertex vertex) {
		Expression exp;
		
		if (vertex instanceof MinusVertex && term.isNegative()) {
			boolean sign = term.isNegative();
			term.setNegative(!sign);
			return match(term, ((MinusVertex) vertex).getInside());
		}
		
		if (term instanceof NumberTerm) {
			if (vertex instanceof NumVertex) {
				exp = match((NumberTerm)term, (NumVertex)vertex);
				if (exp != null) return exp;
			}
			if (vertex instanceof ConstVertex) {
				exp = match((NumberTerm)term, (ConstVertex)vertex);
				if (exp != null) return exp;
			}
			if (vertex instanceof SumIteratorVertex) {
				exp = match((NumberTerm)term, (SumIteratorVertex)vertex);
				if (exp != null) return exp;
			}
			// special case - polynomial (constant)
			if (vertex instanceof ProductVertex) {
				exp = match((NumberTerm)term, (ProductVertex)vertex);
				if (exp != null) return exp;
			}
			if (vertex instanceof ExponentVertex) {
				exp = match((NumberTerm)term, (ExponentVertex)vertex);
				if (exp != null) return exp;
			}
		}
		
		if (term instanceof VariableTerm) {
			if (vertex instanceof VarVertex) {
				exp = match((VariableTerm)term, (VarVertex)vertex);
				if (exp != null) return exp;
			}
			// special case - polynomial (raising to 0 power)
			if (vertex instanceof ExponentVertex) {
				exp = match((VariableTerm)term, (ExponentVertex)vertex);
				if (exp != null) return exp;
			}
		}
		
		if (term instanceof SumTerm) {
			if (vertex instanceof PlusVertex) {
				exp = match((SumTerm)term, (PlusVertex)vertex);
				if (exp != null) return exp;
			}
			if (vertex instanceof SumVertex) {
				exp = match((SumTerm)term, (SumVertex)vertex);
				if (exp != null) return exp;
			}	
		}
		
		if (term instanceof ProductTerm) {
			if (vertex instanceof ProductVertex) {
				exp = match((ProductTerm)term, (ProductVertex)vertex);
				if (exp != null) return exp;
			}	
		}
		
		if (term instanceof FractionTerm) {
			if (vertex instanceof FractionVertex) {
				exp = match((FractionTerm)term, (FractionVertex)vertex);
				if (exp != null) return exp;
			}	
		}
		
		if (term instanceof ExponentiationTerm) {
			if (vertex instanceof ExponentVertex) {
				exp = match((ExponentiationTerm)term, (ExponentVertex)vertex);
				if (exp != null) return exp;
			}
			// special case - match with product where coefficient is equal 1
			if (vertex instanceof ProductVertex) {
				exp = match((ExponentiationTerm)term, (ProductVertex)vertex);
				if (exp != null) return exp;
			}
		}
		
		if (term instanceof RelationTerm) {
			if (vertex instanceof RelationVertex) {
				exp = match((RelationTerm)term, (RelationVertex)vertex);
				if (exp != null) return exp;
			}
		}
		
		// match not found - checking if this is single sum element
		if (vertex instanceof SumVertex) {
			exp = match(term, (SumVertex)vertex);
			if (exp != null) return exp;
		}	
		
		return null;
	}
	
}
